{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center>\n",
    "<img src=\"../../img/ods_stickers.jpg\">\n",
    "\n",
    "## Открытый курс по машинному обучению. Сессия № 3\n",
    "***\n",
    "\n",
    "## Индивидуальный проект по анализу данных на тему: \n",
    "# Прогнозирование оценки отеля путешественником на сайте TripAdvisor\n",
    "\n",
    "#### Автор материала: Чигин Сергей Олегович (@chigin.sergey)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В данной работе предлагается исследовать датасет, содержащий информацию об отзывах с сайта TripAdvisor. 504 отзыва, сделанные в течение 2015 года, оценивают 21 гостиницу. Все гостиницы расположены на Лас-Вегас-Стрип (примерно семикилометровый участок бульвара Лас-Вегас в округе Кларк в штате Невада, США)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import warnings\n",
    "warnings.filterwarnings('ignore')\n",
    "\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "from matplotlib import pyplot as plt\n",
    "import seaborn as sns\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Описание набора данных и признаков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.1 Процесс сбора даннах\n",
    "\n",
    "Данные были взяты из репозитория UCI: https://archive.ics.uci.edu/ml/datasets/Las+Vegas+Strip"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "### 1.2 Описание признаков и целевой переменной"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "Признаки для простоты восприятия можно разбить на три группы: __характеристики пользователя__, __характеристики отзыва__ и __характеристики отеля__. Всего в датасете встречается три типа данных: __binary__ - бинарный, __numeric__ - количественный и __nominal__ - категориальный.  \n",
    "\n",
    "__Характеристики пользователя__:\n",
    "* __User country__ - гражданство пользователя, оставившего текущий отзыв (nominal: 48 стран);\n",
    "* __User continent__ - континент, на котором находится страна пользователя (nominal: 6 континентов);\n",
    "* __Member years__ - кол-во лет, в течение которых пользователь является зарегестрированным на TripAdvisor.com (numeric: от 0 до 13);\n",
    "* __Nr. reviews__ - общее кол-во отзывов, сделанных пользователем на сайте (numeric: от 1 до 775);\n",
    "* __Nr. hotel reviews__ - кол-во отзывов, посвященных отелям (numeric: от 0 до 263);\n",
    "* __Helpful votes__ - общее кол-во лайков у пользователя (numeric: от 0 до 365).\n",
    "\n",
    "__Характеристики отзыва__:\n",
    "* __Review month__ - месяц, во время которого был оставлен отзыв (nominal: 12 месяцев);\n",
    "* __Review weekday__ - день недели, в течение которого был оставлен отзыв (nominal: 7 дней недели);\n",
    "* __Period of stay__ - промежуток времени, в который попало пребывание пользователя в отеле (nominal: 'Dec-Feb', 'Mar-May', 'Jun-Aug' и 'Sep-Nov');\n",
    "* __Traveler type__ - тип путешествия (nominal: 'Couples', 'Families', 'Friends', 'Business' и 'Solo').\n",
    "\n",
    "__Характеристики отеля__:\n",
    "* __Hotel name__ - название отеля (nominal: 21 название отеля);\n",
    "* __Hotel stars__ - кол-во звезд (numeric: от 3 до 5);\n",
    "* __Nr. rooms__ - кол-во номеров в отеле (numeric: от 188 до 4027);\n",
    "* __Pool__ - наличие бассейна (binary: yes/no);\n",
    "* __Gym__ - наличие тренажерного зала (binary: yes/no);\n",
    "* __Tennis court__ - наличие теннисного корта; (binary: yes/no)\n",
    "* __Spa__ - наличие спа-салона (binary: yes/no);\n",
    "* __Casino__ - наличие казино (binary: yes/no);\n",
    "* __Free internet__ - наличие бесплатного интернета (binary: yes/no).\n",
    "\n",
    "__Целевая переменная__:\n",
    "* __Score__ - оценка отеля в текущем отзыве (numeric: от 1 до 5)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.3 Описание решаемой задачи и ее ценность\n",
    "\n",
    "Определение признаков, оказывающих наибольшее влияние на оценку отеля при составлении отзыва.\n",
    "\n",
    "Средняя оценка на сайте, сформированная на основе отзывов, имеет большое значение для путешественника при выборе отеля.   Результаты текущего исследования могут быть полезными для менеджеров отелей в работе по улучшению среднего рейтинга, который оказывает влияние на заполняемость отеля. \n",
    "\n",
    "Изначально текущая задача является задачей __многоклассовой классификации__. Однако, в процессе исследования (см. п.2.2) задача сведется к __бинарной классификации__. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Анализ данных"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Выведем несколько первых строк и размер датасета."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df=pd.read_csv('../../data/LasVegasTripAdvisorReviews-Dataset.csv',sep=';')\n",
    "print('Shape:', df.shape)\n",
    "df[0:3]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "### 2.1 Типы признаков и проверка данных на пропуски"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.info()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Видим, что в датасете нет пропусков - в каждом столбце по 504 наблюдения. __Hotel stars__ при формировании датафрейма определился как категориальный признак (object). Поищем возможную причину.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.unique(df['Hotel stars'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В числе возможных значений присутствуют вещественные числа, дробная часть которых записана через запятую. Именно это и является причиной. Сделаем небольшое преобразование и поменяем тип данных на численный.   "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df['Hotel stars']=df['Hotel stars'].apply(lambda x: x.replace(',','.')).astype('float64')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Все остальные признаки имеют указанный в описании (п.1.2) тип данных. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.get_ftype_counts()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В итоге у нас имеется 7 количественных признаков (6 - целочисленных, 1 - вещественный) и 13 категориальных, среди которых 6 являются бинарными yes/no."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "***\n",
    "_В следующих подпунктах подробно исследована целевая переменная и признаковые группы. Анализ каждой группы последовательно проходит следующие этапы:_ \n",
    "* _вывод основных статистических показателей;_ \n",
    "* _визуализация распределений;_\n",
    "* _оценка взаимодействий между признаками внутри группы;_\n",
    "* _оценка влияния признаков на целевую переменную._\n",
    "\n",
    "_Таким образом п.2 включает в себя одновременно и первичный анализ данных, и визуальный анализ данных, который по умолчанию должен был идти следующим пунктом. В связи с этим после п.2 сразу же идет п.4, где собраны и прокомментированны все найденные во время анализа зависимости._\n",
    "***"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.2 Анализ целевой переменной"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В большинстве случаев очень полезную информацию об отеле несет само содержание отзыва, из которого вытекает поставленная юзером оценка. К сожалению, в текущем датасете эта информация не представлена. Помимо этого у датасета очень небольшой объем - всего 504 объекта. В связи с этим было принято решение немного упростить задачу и свести ее к __бинарной классификации__: 1 - хороший отзыв (оценки 4 и 5), 0 - плохой отзыв (оценки 1, 2 и 3).\n",
    "\n",
    "Посмотрим на распределение начальной целевой переменной __Score__, а затем на распределение новой __new_score__."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.set(style=\"darkgrid\", rc={'figure.figsize':(16,5)})\n",
    "fig, ax =plt.subplots(1,2)\n",
    "sns.countplot(df['Score'], ax=ax[0])\n",
    "df['new_score']=df['Score'].apply(lambda x: 0 if x in [1,2,3] else 1)\n",
    "sns.countplot(df['new_score'], ax=ax[1]);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df['new_score'].value_counts(normalize=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Видно, что в распределении __new_score__ присутствует дисбаланс. Хороших отзывов примерно в 3.5 раза больше. Будем учитывать это при работе с моделью."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.3 Анализ характеристик пользователя"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Количественные признаки"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим на основные статистические параматеры."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[['User country', 'User continent','Nr. reviews', 'Nr. hotel reviews', 'Helpful votes', 'Member years']].describe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сразу же бросается в глаза отрицательное значение __min__ у __Member years__. Проверим, нет ли еще отрицательных значений у этого признака.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.loc[df['Member years']<0, 'Member years']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Да, оно единственное. Скорее всего была просто допущена ошибка при формировании датасета. Исправим ее путем замены на медианное значение."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.loc[df['Member years']==df['Member years'].min(), 'Member years']=df['Member years'].median()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сделаем pairplot для этих признаков. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.pairplot(df[['Nr. reviews', 'Nr. hotel reviews', 'Helpful votes', 'Member years']], size=2.7, diag_kind='hist');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "У __Nr. reviews, Nr. hotel reviews__ и __Helpful votes__ распределения очень далеки от нормальных, у __Member years__ ситуация получше. По scatter plots видно, что __Member years__ с кем-то из признаков особо никак не взаимодействует. О взаимодействиях между остальными судить довольно сложно - точки слишком кучкуются около начала координат. Добавим к признакам целевую переменную __new_score__ и построим матрицу корреляций. Признаки численные - коэффициенты рассчитываем по Пирсону.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "corr_matrix_user=df[['Nr. reviews', 'Nr. hotel reviews', 'Helpful votes', 'Member years', 'new_score']].corr(method='pearson')\n",
    "sns.heatmap(corr_matrix_user, annot=True, fmt=\".2f\", linewidths=.5, cmap='YlGnBu');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Видим, что у __Nr. reviews, Nr. hotel reviews__ и __Helpful votes__ достаточно высокие коэффициенты. Это нормально. Логично предположить, что у юзера с большим кол-вом рецензий на сайте будет и большое кол-во отзывов непосредственно об отелях. Тоже самое и по лайкам. Их кол-во напрямую зависит от кол-ва рецензий. Целевая переменная ни с одним из признаков не коррелирует. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Категориальные признаки"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[['User country', 'User continent','Nr. reviews', \n",
    "    'Nr. hotel reviews', 'Helpful votes', 'Member years']].describe(include='object')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.set(style=\"darkgrid\", rc={'figure.figsize':(16,5)})\n",
    "fig, ax =plt.subplots(1,2)\n",
    "sns.countplot(df['User continent'], order=df['User continent'].value_counts().index, ax=ax[1])\n",
    "top10=df[df['User country'].isin(df['User country'].value_counts().head(10).index)]['User country']\n",
    "sns.countplot(top10, order=top10.value_counts().index, ax=ax[0]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Для __User country__ выведены 10 самых популярных стран в датасете. Всего же представлено 48 стран. Вычислим какая доля отзывов приходится на топ-10 от общего числа."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Share of top 10 countries:', len(top10)/df.shape[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Создадим __User country new__, немного преобразовав __User country__ путем сокращения кол-ва уникальных значений до 11. В них будут входить определенные выше старна топ-10 и новое значение other, объединяющее оставшиеся 38 стран, на чью долю приходится около 13% отзывов от общего числа."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df['User country new']=df['User country'].apply(lambda x: 'Other' if x not in np.unique(top10) else x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Изобразим распределение хороших и плохих отзывов и по __User continent__, и по __User country new__."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.set(style=\"darkgrid\", rc={'figure.figsize':(16,10)})\n",
    "fig, ax =plt.subplots(2,1)\n",
    "sns.countplot(x='User continent', hue='new_score', data=df, order=df['User continent'].value_counts().index, ax=ax[0]);\n",
    "sns.countplot(x='User country new', hue='new_score', data=df, \n",
    "              order=df['User country new'].value_counts().index, ax=ax[1]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Здесь стоит вспомнить зафиксированную в п.2.2 долю отрицательных отзывов в 22,5% от общего кол-ва во всем датасете. Из распределений выше видно, что в абсолютном бол-ве объектов доля негативных отзывов примерно такая же или ниже. Из континентов исключениями являются Азия и Африка, из стран - Канада, Германия, Египет и группа стран, не вошедших в топ-10. У них доля плохих отзывов выше.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Доли плохих отзывов среди континентов в порядке убывания:')\n",
    "1-df.groupby([df['User continent']])['new_score'].agg([np.mean]).sort_values(by='mean', ascending=True).T"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Доли плохих отзывов среди стран в порядке убывания:')\n",
    "1-df.groupby([df['User country new']])['new_score'].agg([np.mean]).sort_values(by='mean', ascending=True).T"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Но на страны или континенты, которые являются исключениями, приходится довольно небольшое кол-во отзывов всего. Тот факт, что у них высокие доли отрицатальных отзывов нельзя считать четкой зависимостью.    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.4 Анализ характеристик отзыва"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Все признаки, характеризующие отзыв, имеют __категориальный__ тип данных. Посмотрим на их общую статистику.    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[['Period of stay', 'Traveler type', 'Review month', 'Review weekday']].describe(include='object')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Period of stay__ и __Review month__ определяют временной отрезок, за который у нас имеются данные. Выведем распределение отзывов по ним."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df['Period of stay'].value_counts()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df['Review month'].value_counts()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "За каждый месяц в 2015 году было взято ровно по 42 отзыва. По сезонам отзывы распределены примерно равномерно. \n",
    "\n",
    "Визуализируем распределения оставшихся двух признаков (__Traveler type__ и __Review weekday__)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.set(style=\"darkgrid\", rc={'figure.figsize':(16,5)})\n",
    "fig, ax =plt.subplots(1,2)\n",
    "sns.countplot(df['Traveler type'], ax=ax[0])\n",
    "sns.countplot(df['Review weekday'], ax=ax[1]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим на то, как меняется кол-во отдыхающих каждого типа от сезона к сезону."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.countplot(x='Period of stay', hue='Traveler type', data=df);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Семьи чаще выбираются на отдых летом, в это же время падает кол-во бизнес-трипов. В зимнее время друзья путешествуют вместе реже, чем в остальные сезоны.\n",
    "\n",
    "Построим похожий countplot для __Review month__ и __Period of stay__. Интересно посмотреть есть ли значительные отставания между написанием отзыва и временем пребывания в отеле.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.set(style=\"darkgrid\", rc={'figure.figsize':(16,5)})\n",
    "sns.countplot(x='Review month', hue='Period of stay', data=df);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Очень странная ситуация с февралем. Есть несколько отзывов, которые были написаны в этом месяце, хотя период пребывания в отеле значится __Mar-May__. Как будто отзыв об отеле был составлен еще до самого пребывания там. Выведем отдельно эти отзывы. С остальными месяцами все в порядке.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[(df['Review month']=='February') & (df['Period of stay']=='Mar-May')]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Оба отзыва являются положительными. Скорее всего была просто допущена ошибка при формировании датасета. Чуть выше мы выводили распределение отзывов по сезонам, оно было практически равномерным. Сделаем это еще раз."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df['Period of stay'].value_counts()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Очевидно, что распределение было бы полностью равномерным, если бы не эта ошибка с двумя отзывами. Исправим ее, поменяв у них __Period of stay__ на __Dec-Feb__."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.loc[(df['Review month']=='February') & (df['Period of stay']=='Mar-May'), 'Period of stay']='Dec-Feb'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Построим countplot с распределениями __new_score__ для __Period of stay__ и __Review month__. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.set(style=\"darkgrid\", rc={'figure.figsize':(16,8)})\n",
    "fig, ax =plt.subplots(2,1)\n",
    "sns.countplot(x='Review month', hue='new_score', data=df, ax=ax[0]);\n",
    "sns.countplot(x='Period of stay', hue='new_score', data=df, ax=ax[1]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Смотря на диаграмму по месяцам, стоит вспомнить, что кол-во отзывов в течение каждого месяца одинаково и равно 42. На май приходится самое большое кол-во плохих отзывов. \n",
    "\n",
    "Диаграмма по __Period of stay__ по сути является более грубой версией диаграммы по __Review month__. После исправления ошибки в данных и проверки на отсутствие значительных лагов между пребыванием в отеле и написанием отзыва стало понятно, что эти два признака дублируют друг друга. Разница только во временном отрезке (месяц у __Review month__ и три месяца у __Period of stay__). При предобратке данных признак __Period of stay__ будет необходимо удалить."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "Посмотрим на доли плохих отзывов по __Traveler type__ и __Review weekday__."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Доли плохих отзывов среди типов путешествий в порядке убывания:')\n",
    "1-df.groupby([df['Traveler type']])['new_score'].agg([np.mean]).sort_values(by='mean', ascending=True).T"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "Выше всего у __Families__, но на первом месте они с небольшим отрывом. От __Traveler type__ сильно распределение целевой переменной не зависит."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Доли плохих отзывов среди дней недели написания отзыва в порядке убывания:')\n",
    "1-df.groupby([df['Review weekday']])['new_score'].agg([np.mean]).sort_values(by='mean', ascending=True).T"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "Примерно тоже самое можно сказать и про __Review weekday__. Нет какого-то конкретного дня, который аккумулировал на себе прям сильно большую долю плохих отзывов в сравнении с другими днями."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.5 Анализ характеристик отеля"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Категориальные признаки"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[['Hotel name', 'Pool', 'Gym', 'Tennis court', 'Spa', 'Casino', 'Free internet', \n",
    "    'Hotel stars', 'Nr. rooms']].describe(include='object')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Цифры по __Hotel name__ подтверждают анонсированное в описании распределение - суммарно 504 отзыва о 21 отеле. Посмотрим каким образом распределились отзывы среди этого признака. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df['Hotel name'].value_counts()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Они распределены равномерно - по 24 на каждый.\n",
    "\n",
    "Визуализируем распределения 6-ти бинарных (yes/no) признаков, отвечающих за гостиничные услуги."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.set(style=\"darkgrid\", rc={'figure.figsize':(16,8)})\n",
    "fig, ax =plt.subplots(nrows=2, ncols=3, sharex='all', sharey='all')\n",
    "sns.countplot(df['Pool'], ax=ax[0,0])\n",
    "sns.countplot(df['Gym'], ax=ax[0,1])\n",
    "sns.countplot(df['Tennis court'], ax=ax[0,2])\n",
    "sns.countplot(df['Spa'], ax=ax[1,0])\n",
    "sns.countplot(df['Casino'], ax=ax[1,1])\n",
    "sns.countplot(df['Free internet'], ax=ax[1,2]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Бассейн и спа в большинстве отелей отсутствует. Стоит также отметить, что есть отели, пускай их и не много, у которых нет бесплатного интернета."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Количественные признаки"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[['Hotel name', 'Pool', 'Gym', 'Tennis court', 'Spa', 'Casino', 'Free internet', \n",
    "    'Hotel stars', 'Nr. rooms']].describe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сразу посмотрим на распределение по __Hotel stars__ и выведем кол-во уникальных значений у __Nr. rooms__."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.set(style=\"darkgrid\", rc={'figure.figsize':(8,5)})\n",
    "sns.countplot(df['Hotel stars']);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "len(np.unique(df['Nr. rooms']))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "21 уникальное значение говорит о том, что все гостиницы имеют разные кол-ва номеров. Посмотрим на их распределение."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.distplot(df['Nr. rooms']);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Взаимодействия признаков и влияние на целевую переменную"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "Перекодируем бинарные признаки yes/no и построим матрицу корреляций."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "binary_features=['Pool', 'Gym', 'Tennis court', 'Spa','Casino', 'Free internet']\n",
    "data_bin=df[binary_features]\n",
    "data_bin_dum=pd.get_dummies(data_bin)\n",
    "data_bin_cut=data_bin_dum.iloc[:,1::2]\n",
    "df_3=pd.concat([data_bin_cut, df[['Hotel stars', 'Nr. rooms', 'new_score']]],axis=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Бинарные признаки были разбиты на взаимодополняющие пары (пример: __Pool_YES__ и __Pool_NO__). Из каждой пары были удалены по одному признаку, посколько они не добавляют никакой информации. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "corr_matrix_hotel=df_3.corr()\n",
    "sns.heatmap(corr_matrix_hotel, annot=True, fmt=\".2f\", linewidths=.5, cmap='YlGnBu');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "__new_score__, пускай коэффициенты и невысокие, но коррелирует с тремя признаками: __Hotel stars__, __Free internet_YES__ и __Pool_YES__.\n",
    "\n",
    "Так же в матрице наблюдается довольно большое кол-во относительно высоких коэффициентов между признаками. Логика в этом простая. Условно, если есть в отеле казино, то наверняка в нем будет и спа, и спортзал и тд. Но среди признаков нет тех, которые бы дублировали друг друга. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Инсайты, найденные зависимости"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "* Целевая переменая __new_score__ не сбалансирована. Хороших отзывов примерно в 3.5 раза больше, чем плохих;\n",
    "* Признак __Helpful votes__ сильно коррелирует с __Nr. reviews__ и __Nr. hotel reviews__ (0.79 и 0.76 соответственно) - его можно отбросить;\n",
    "* __User country__/__User country new__ и __User continent__ несут в себе одинаковую информацию. Один признак распределяет объекты на 48/11 категорий, второй - на 6. При этом оба признака не имеют большого влияния на __new_score__. Первый признак уберем, а второй (__User continent__) оставим. При его кодировании признаковое пространство не будет сильно раздуто;\n",
    "* Аналогичная ситуация с __Review month__ и __Period of stay__. Оставляем только __Review month__;\n",
    "* Была обнаружена некая корреляция с __Hotel stars__, __Free internet__ и __Pool__. Логично предположить, что класс отеля и наличие услуг, которые обеспечивают комфорт и удобство клиентов, напрямую влияют на впечатление от пребывания в нем;\n",
    "* Признак __Hotel name__ содержит только названия отелей и он не несет никакой дополнительной информации о самом отеле, его можно убрать.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "## 5. Выбор метрики"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "Мы решаем задачу бинарной классификации. Допустимые метрики качества в таком случае:\n",
    "* Accuracy\n",
    "* Precision\n",
    "* Recall\n",
    "* F1-score\n",
    "* ROC-AUC\n",
    "* Logistic loss\n",
    "\n",
    "В нашей задаче отсутствует баланс классов. В этом случае наилучшей метрикой для оценки качества модели будет __ROC-AUC__. Она позволит оценить модель вцелом, не привязываясь к какому-то конкретному порогу."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "## 6. Выбор модели"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "Мои текущие знания моделей на текущий момент не очень обширные. Я в Data Science - новичок. Знаком только с теми, которые были рассмотрены в рамках курса. Среди них следующие алгоритмы подходят для задачи бинарной классификации: \n",
    "* KNeighborsClassifier()\n",
    "* DecisionTreeClassifier()\n",
    "* RandomForestClassifier()   \n",
    "* LogisticRegression()\n",
    "* XGBCClassifier()\n",
    "\n",
    "Попробуем их с базовыми настройками. Потом отберем те, которые показали лучшие результаты, и продолжим исследование уже только с ними."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "## 7. Предобработка данных"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Во время анализа данных уже были сделаны небольшие преобраование и исправлены несколько ошибок. В п.4 были перечислены признаки, которые использоваться не будут. Соберем в отдельный датафрейм те, с которыми будем работать, и разобьем их на две группы: количественные и категориальные."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "from scipy.sparse import hstack\n",
    "from sklearn.model_selection import train_test_split, GridSearchCV \n",
    "\n",
    "final_df=df[['Nr. reviews', 'Nr. hotel reviews', 'Member years', 'Hotel stars', 'Nr. rooms',\n",
    "            'User continent', 'Review month', 'Review weekday', 'Traveler type',\n",
    "                         'Pool', 'Gym', 'Tennis court', 'Spa','Casino', 'Free internet','new_score']]\n",
    "\n",
    "cat_features=['User continent', 'Review month', 'Review weekday', 'Traveler type',\n",
    "                         'Pool', 'Gym', 'Tennis court', 'Spa','Casino', 'Free internet']\n",
    "\n",
    "num_features=['Nr. reviews', 'Nr. hotel reviews', 'Member years', 'Hotel stars', 'Nr. rooms']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Разобьем данные на тренировочную, валидационную и тестовую выборки. Разбивку будем делать с учетом текущего баланса классов."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train, X_test, y_train, y_test = train_test_split(final_df.drop('new_score', axis=1), final_df['new_score'], \n",
    "                                                    test_size=0.2, random_state=17, stratify=final_df['new_score'])\n",
    "X_valid, X_test, y_valid, y_test = train_test_split(X_test, y_test, test_size=0.5, random_state=17)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train.shape, X_valid.shape, X_test.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Количественные признаки отмасшатабируем с помощью __StandardScaler()__. Категориальные признаки преобразуем с помощью __dummy__ кодирования."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_scale_train = X_train[num_features]\n",
    "data_scale_test = X_valid[num_features]\n",
    "scaler = StandardScaler()\n",
    "data_scaled_train = scaler.fit_transform(data_scale_train)\n",
    "data_scaled_valid = scaler.transform(data_scale_test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_dum_train = X_train[cat_features]\n",
    "data_dum_valid = X_valid[cat_features]\n",
    "\n",
    "data_dum_full=pd.concat([data_dum_train, data_dum_valid], axis=0)\n",
    "idx_split = data_dum_train.shape[0]\n",
    "\n",
    "data_dummy_full=pd.get_dummies(data_dum_full)\n",
    "data_dummy_train = data_dummy_full[:idx_split]\n",
    "data_dummy_valid = data_dummy_full[idx_split:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Объедим уже преобразованные признаки и приведем их к виду разряженных матриц."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from scipy.sparse import csr_matrix\n",
    "X_train_sparse = csr_matrix(hstack([data_scaled_train, data_dummy_train]))\n",
    "\n",
    "X_valid_sparse = csr_matrix(hstack([data_scaled_valid, data_dummy_valid])) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 8. Кросс-валидация и настройка гиперпараметров модели"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8-1. Кросс-валидация"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "На кросс-валидации будем разбивать тренировочную выборку на 5 фолдов с учетом баланса классов."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "from sklearn.tree import DecisionTreeClassifier\n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "from xgboost import XGBClassifier\n",
    "\n",
    "\n",
    "from sklearn.model_selection import cross_val_score, StratifiedKFold\n",
    "from sklearn.metrics import roc_auc_score\n",
    "\n",
    "skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=17)\n",
    "\n",
    "models = [\n",
    "    KNeighborsClassifier(),\n",
    "    DecisionTreeClassifier(random_state = 17),\n",
    "    RandomForestClassifier(n_estimators=100, random_state = 17), \n",
    "    XGBClassifier(random_state = 17),\n",
    "    LogisticRegression(random_state = 17)\n",
    "    \n",
    "]\n",
    "\n",
    "for model in models:\n",
    "    model.fit(X_train_sparse, y_train)\n",
    "    score = np.mean(cross_val_score(model, X_train_sparse, y_train, cv=skf, scoring='roc_auc'))\n",
    "    print('roc auc:', score)\n",
    "    print(model)\n",
    "    print()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Вцелом скор довольно невысокий у всех, но лучший у __LogisticRegression__. Оставляем ее и попробуем улучшить как-то качество с помощью работы над признаковым пространством."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 9. Создание новых признаков и описание этого процесса"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В данных преимущественно категориальные признаки. Мощной техникой для работы в этом случае является учет попарных взаимодействий признаков (feature interactions). Построим попарные взаимодействия всех категориальных признаков."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9.1 Попарные взаимодействия признаков"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_interact = X_train[['User continent', 'Review month', 'Review weekday', 'Traveler type','Pool', \n",
    "                       'Gym', 'Tennis court', 'Spa','Casino', 'Free internet']]\n",
    "\n",
    "cat_features = train_interact.columns[train_interact.dtypes == 'object']\n",
    "for i, col1 in enumerate(cat_features):\n",
    "    for j, col2 in enumerate(cat_features[i + 1:]):\n",
    "        train_interact[col1 + '_' + col2] = train_interact[col1] + '_' + train_interact[col2] "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_interact.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dummy_train_interact = pd.get_dummies(train_interact)\n",
    "X_train_interact_sparse = csr_matrix(hstack([data_scaled_train, dummy_train_interact]))\n",
    "X_train_interact_sparse.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ох, после объединения с 5-ю отскалированными количественными получилось 735 признаков - это больше чем тренировочных объектов. Вряд ли это приведет к каким-то улучшениями, но все равно попробуем."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model=LogisticRegression(random_state = 17)\n",
    "\n",
    "model.fit(X_train_sparse, y_train)\n",
    "score = np.mean(cross_val_score(model, X_train_interact_sparse, y_train, cv=skf, scoring='roc_auc'))\n",
    "print('roc auc:', score)\n",
    "print(model)\n",
    "print()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Да, скор упал. Но у нас есть еще 5 количественных признаков, двое из которых имели распределения с сильным скосом вправо.  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9.2 Логарифмирование количественных признаков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим как изменятся распределения __Nr. reviews__ и __Nr. hotel reviews__ после логарифмирования."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.log(final_df[['Nr. reviews', 'Nr. hotel reviews']]+1).hist();"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Стали очень похожи на нормальные распределения. Попробуем посмотреть на скор с прологарифмированными __Nr. reviews__ и __Nr. hotel reviews__."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train[['Nr. reviews', 'Nr. hotel reviews']]=np.log(X_train[['Nr. reviews', 'Nr. hotel reviews']]+1)\n",
    "X_valid[['Nr. reviews', 'Nr. hotel reviews']]=np.log(X_valid[['Nr. reviews', 'Nr. hotel reviews']]+1)\n",
    "\n",
    "data_scale_train = X_train[num_features]\n",
    "data_scale_valid = X_valid[num_features]\n",
    "scaler = StandardScaler()\n",
    "data_scaled_train = scaler.fit_transform(data_scale_train)\n",
    "data_scaled_valid = scaler.transform(data_scale_valid)\n",
    "\n",
    "data_dum_train = X_train[cat_features]\n",
    "data_dum_valid = X_valid[cat_features]\n",
    "\n",
    "data_dum_full=pd.concat([data_dum_train, data_dum_valid], axis=0)\n",
    "idx_split = data_dum_train.shape[0]\n",
    "\n",
    "data_dummy_full=pd.get_dummies(data_dum_full)\n",
    "data_dummy_train = data_dummy_full[:idx_split]\n",
    "data_dummy_valid = data_dummy_full[idx_split:]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_sparse = csr_matrix(hstack([data_scaled_train, data_dummy_train]))\n",
    "\n",
    "X_valid_sparse = csr_matrix(hstack([data_scaled_valid, data_dummy_valid])) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.fit(X_train_sparse, y_train)\n",
    "score = np.mean(cross_val_score(model, X_train_sparse, y_train, cv=skf, scoring='roc_auc'))\n",
    "print('roc auc:', score)\n",
    "print(model)\n",
    "print()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Есть небольшие улучшения, скор подрос. Попробуем понастраивать гиперпараметры."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 8. Кросс-валидация и настройка гиперпараметров модели"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8-2. Настройка гиперпараметров модели"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "У LogisticRegression() будем настраивать следующий параметр:\n",
    "* С - обратное значение коэффициента регуляризации."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "params={'C': np.logspace(-3, 1, 10)}\n",
    "\n",
    "model = LogisticRegression(random_state=17)\n",
    "\n",
    "lr_grid = GridSearchCV(model, param_grid=params, cv=skf, scoring='roc_auc', verbose=True, n_jobs=-1)\n",
    "lr_grid.fit(X_train_sparse,y_train)\n",
    "print(lr_grid.best_score_)\n",
    "print(lr_grid.best_params_)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Проверим лучшую модель на валидационной выборке."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "logit=lr_grid.best_estimator_\n",
    "\n",
    "logit.fit(X_train_sparse, y_train)\n",
    "logit_valid_pred = logit.predict_proba(X_valid_sparse)[:, 1]\n",
    "roc_auc_score(y_valid, logit_valid_pred)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "logit"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 10. Построение кривых валидации и обучения"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сначала посмотрим на кривые обучения."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.model_selection import learning_curve\n",
    "\n",
    "def plot_with_err(x, data, **kwargs):\n",
    "    mu, std = data.mean(1), data.std(1)\n",
    "    lines = plt.plot(x, mu, '-', **kwargs)\n",
    "    plt.fill_between(x, mu - std, mu + std, edgecolor='none',\n",
    "                     facecolor=lines[0].get_color(), alpha=0.2)\n",
    "\n",
    "def plot_learning_curve():\n",
    "    train_sizes = np.linspace(0.05, 1, 20)\n",
    "    \n",
    "    N_train, val_train, val_test = learning_curve(logit, X_train_sparse, y_train, train_sizes=train_sizes, cv=skf,\n",
    "                                                  scoring='roc_auc')\n",
    "    plot_with_err(N_train, val_train, label='training scores')\n",
    "    plot_with_err(N_train, val_test, label='validation scores')\n",
    "    plt.xlabel('Training examples'); \n",
    "    plt.ylabel('ROC-AUC')\n",
    "    plt.legend()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=(14, 8))\n",
    "plot_learning_curve()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В принципе, наблюдается хорошая тенденция – кривые постепенно сходятся. Можно предположить, что при дальнейшем движении направо качество на валидации можно будет повысить. Переходим к кривым валидации. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.model_selection import validation_curve\n",
    "\n",
    "C_range=np.logspace(-3, 1, 10)\n",
    "\n",
    "val_train, val_test = validation_curve(model, X_train_sparse, y_train, param_name='C', \n",
    "                                       param_range=C_range, cv=skf, scoring='roc_auc')\n",
    "\n",
    "plt.figure(figsize=(14, 8))\n",
    "plot_with_err(C_range, val_train, label='training scores')\n",
    "plot_with_err(C_range, val_test, label='validation scores')\n",
    "plt.xlabel('C')\n",
    "plt.ylabel('ROC AUC')\n",
    "plt.legend();"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "У С есть явный пик. При увеличении параметра скор падает, но не резко и не значительно."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 11. Прогноз на тестовой выборке"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Объедими тренировочную выборку с валидационной и обучим на них модель. Затем проверим ее качество на тестовой выборке."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train = X_train.append(X_valid)\n",
    "y_train = y_train.append(y_valid)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_test[['Nr. reviews', 'Nr. hotel reviews']]=np.log(X_test[['Nr. reviews', 'Nr. hotel reviews']]+1)\n",
    "\n",
    "data_scale_train = X_train[num_features]\n",
    "data_scale_test = X_test[num_features]\n",
    "scaler = StandardScaler()\n",
    "data_scaled_train = scaler.fit_transform(data_scale_train)\n",
    "data_scaled_test = scaler.transform(data_scale_test)\n",
    "\n",
    "data_dum_train = X_train[cat_features]\n",
    "data_dum_test = X_test[cat_features]\n",
    "\n",
    "data_dum_full=pd.concat([data_dum_train, data_dum_test], axis=0)\n",
    "idx_split = data_dum_train.shape[0]\n",
    "\n",
    "data_dummy_full=pd.get_dummies(data_dum_full)\n",
    "data_dummy_train = data_dummy_full[:idx_split]\n",
    "data_dummy_test = data_dummy_full[idx_split:]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_sparse = csr_matrix(hstack([data_scaled_train, data_dummy_train]))\n",
    "\n",
    "X_test_sparse = csr_matrix(hstack([data_scaled_test, data_dummy_test])) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "logit.fit(X_train_sparse, y_train)\n",
    "logit_test_pred = logit.predict_proba(X_test_sparse)[:, 1]\n",
    "roc_auc_score(y_test, logit_test_pred)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Результат на тестовой выборке лучше. Думаю, что причина кроется в большем кол-ве данных, на которых модель обучалась. Мы словно проверили на практике предположение, сделанное по кривым обучения в п.10. Как раз объединив тренировочную выборку с валидационной, мы сместились немного вправо. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 12. Выводы"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Изначально целью иследования было определение факторов, которые сильнее всего оказывают влияние на оценку. Давайте посмотрим на веса модели."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "names=data_scale_train.columns.append(data_dummy_full.columns)\n",
    "\n",
    "def getCoefficients(model):\n",
    "    \"\"\"\n",
    "    Возвращает отсортированные по абсолютному значению коэффициенты модели\n",
    "    \"\"\"\n",
    "    \n",
    "    coefs = pd.DataFrame(model.coef_[0], names)\n",
    "    coefs.columns = [\"coef\"]\n",
    "    coefs[\"abs\"] = coefs.coef.apply(np.abs)\n",
    "    return coefs.sort_values(by=\"abs\", ascending=False).drop([\"abs\"], axis=1)    \n",
    "    \n",
    "\n",
    "def plotCoefficients(model):\n",
    "    \"\"\"\n",
    "    Отрисовывает отсортированные по абсолютному значению коэффициенты модели\n",
    "    \"\"\"\n",
    "    coefs = getCoefficients(model)\n",
    "    \n",
    "    plt.figure(figsize=(20, 10))\n",
    "    coefs.coef.plot(kind='bar')\n",
    "    plt.grid(True, axis='y')\n",
    "    plt.hlines(y=0, xmin=0, xmax=len(coefs), linestyles='dashed');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plotCoefficients(logit)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Результаты вряд ли можно назвать удивительными. Сильнее всего влияет на оценку кол-во звезд у отеля и наличи бесплатного интернета. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "На данный момент, качество модели не очень высокое, но убежден, что у исследования есть потенциал. Следующие пункты, наверняка помогли бы его улучшить: \n",
    "* увеличение объема данных (здесь речь непосредственно о кол-ве объектов в датасете);\n",
    "* добавление новых признаков с полезной информацией (текст отзыва, год постройки отеля, численность обслуживающего персонала, языки, на которых говорит персонал и тд);\n",
    "* охват больших территорий, на которых расположены отели (разные города, разные страны)."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
